package castledefense.game.render.effects;

import java.util.Random;

import castledefense.game.render.Cam;
import castledefense.game.render.GraphicsUtil;
import castledefense.logic.Map;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.math.Vector2;

/**
 * Awesome lightning system. Usage -create one LightningSystem()
 * 
 * -create lightnings with: public final void creator(float x1, float y1, float
 * x2, float y2) {
 * 
 * -remember call these:
 * 
 * public final void update(int delta) {
 * 
 * public final void render(Graphics g, float scale)
 * 
 * It's super hacky but its fast.
 * */

public final class LightningHandler {

	private static final int MAX_COUNT = 64;
	private static final int MAX = 128;
	private static final int MAX2 = MAX * 2;
	private int last = 0;

	private final LightningBoltEffect lightingBoltEffect[] = new LightningBoltEffect[MAX_COUNT];
	private final boolean[] drawable = new boolean[MAX_COUNT];

	private final Beam segments[] = new Beam[MAX];
	private final Beam newSegments[] = new Beam[MAX2];

	private Beam segment;
	private final Random random = new Random();

	protected final Vector2 tmpVec = new Vector2();
	protected final Vector2 midPoint = new Vector2();
	protected final Vector2 direction = new Vector2();
	protected final Vector2 perpendicular = new Vector2();

	public LightningHandler() {
		final float[] x1 = new float[MAX2];
		final float[] y1 = new float[MAX2];
		for (int i = 0; i < MAX_COUNT; i++) {
			lightingBoltEffect[i] = new LightningBoltEffect();
			polygonFast(lightingBoltEffect[i].mesh, x1, y1, MAX2);

		}
		for (int i = 0; i < MAX; i++) {
			segments[i] = new Beam();
		}
		for (int i = 0; i < MAX2; i++) {
			newSegments[i] = new Beam();
		}
	}

	public void create(float x1, float y1, float x2, float y2) {
		last = (last < MAX_COUNT - 1) ? last + 1 : 0;
		generateLightningBolt(new Vector2(x1 * Map.cellWidth, y1
				* Map.cellWidth), new Vector2(x2 * Map.cellWidth, y2
				* Map.cellWidth), 400, lightingBoltEffect[last]);
	}

	public void update(int delta) {
		for (int i = 0; i < MAX_COUNT; i++) {
			drawable[i] = !lightingBoltEffect[i].update(delta);
		}
	}

	public void render() {
		GraphicsUtil.startBlend();
		Gdx.gl.glLineWidth(3f / Cam.getZoom());
		for (int i = 0; i < MAX_COUNT; i++) {
			if (drawable[i]) {
				lightingBoltEffect[i].renderMesh();
			}
		}
		Gdx.gl10.glColor4f(1f, 1f, 1f, 1f);
		GraphicsUtil.endBlend();
	}

	protected void generateLightningBolt(Vector2 p0, Vector2 p1, int duration,
			LightningBoltEffect lb) {
		int size = 0;
		segments[size++].create(p0, p1);
		int offset = 128;
		float height = 60.0f;
		float probability = 0.25f * (1 + random.nextFloat());
		int partitions = 5;

		for (int i = 0; i < partitions; i++) {

			final int tmpSize = size;
			size = 0;
			for (int j = 0; j < tmpSize; j++) {

				segment = segments[j];
				midPoint.set((segment.vec1.x + segment.vec2.x) / 2,
						(segment.vec1.y + segment.vec2.y) / 2);

				perpendicular.set(midPoint.y, midPoint.x);
				perpendicular.nor().mul((random.nextFloat() - 0.5f) * offset);
				midPoint.add(perpendicular);

				if (random.nextFloat() < probability) {
					// generate new branch
					tmpVec.set(midPoint);
					direction.set(tmpVec.sub(segment.vec1));
					tmpVec.set(midPoint);
					direction.rotate(((random.nextFloat() - 0.5f) * height));
					newSegments[size++].create(midPoint, tmpVec.add(direction));
				}

				newSegments[size++].create(segment.vec1, midPoint);
				newSegments[size++].create(midPoint, segment.vec2);
			}
			final int tmp = (size < MAX ? size : MAX);
			for (int j = 0; j < tmp; j++) {
				segments[j].create(newSegments[j].vec1, newSegments[j].vec2);
			}
			offset >>= 1;
		}

		lb.currentTime = duration;
		lb.totalTime = duration;

		size = (size < 128 ? size : 128);

		final short indices[] = new short[size << 1];
		for (int i = 0; i < size; i++) {
			int tmpI = i << 2;
			verticesPOOL[tmpI] = segments[i].vec1.x;
			verticesPOOL[tmpI + 1] = segments[i].vec1.y;
			verticesPOOL[tmpI + 2] = segments[i].vec2.x;
			verticesPOOL[tmpI + 3] = segments[i].vec2.y;
			tmpI = i << 1;
			indices[tmpI] = (short) (tmpI);
			indices[tmpI + 1] = (short) (tmpI + 1);
		}
		size <<= 1;
		lb.size = size;
		lb.mesh.setVertices(verticesPOOL, 0, size << 1);
		lb.mesh.setIndices(indices, 0, size);

	}

	private float verticesPOOL[] = new float[MAX2 * 2];

	/** MAX VERTICE SIZE 256!! */
	private void polygonFast(Mesh mesh, float x[], float y[], int length) {
		final short indices[] = new short[length];
		for (short a = 0; a < length; a++) {
			verticesPOOL[a << 1] = x[a];
			verticesPOOL[(a << 1) + 1] = y[a];
			indices[a] = a;
		}
		mesh.setVertices(verticesPOOL, 0, length << 1);
		mesh.setIndices(indices, 0, length);

	}

	private class Beam {
		final Vector2 vec1 = new Vector2();
		final Vector2 vec2 = new Vector2();

		final void create(Vector2 v1, Vector2 v2) {
			vec1.set(v1);
			vec2.set(v2);
		}
	}

	private class LightningBoltEffect {
		int currentTime;
		int totalTime;
		int size = 0;
		final Mesh mesh = new Mesh(false, MAX2, MAX2, new VertexAttribute(
				Usage.Position, 2, "line_positions"));

		final boolean update(int delta) {
			currentTime = (currentTime > 0) ? (currentTime - delta) : 0;
			return currentTime == 0;
		}

		final void renderMesh() {
			final float alpha = (float) currentTime / (float) totalTime;
			final float a = 0.5f * (0.9f + random.nextFloat());
			Gdx.gl10.glColor4f(a, a, 1f, a * alpha);
			mesh.render(GL10.GL_LINES, 0, size);
		}
	}
}
